package com.atguigu.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import com.atguigu.gulimall.search.constant.EsConstant;
import com.atguigu.gulimall.search.feign.ProductFeignService;
import com.atguigu.gulimall.search.service.MallSearchService;
import com.atguigu.gulimall.search.vo.AttrResponseVo;
import com.atguigu.gulimall.search.vo.BrandVo;
import com.atguigu.gulimall.search.vo.SearchParam;
import com.atguigu.gulimall.search.vo.SearchResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {
    @Autowired
    RestHighLevelClient restClient;
    @Autowired
    ProductFeignService productFeignService;


    @Override
    public SearchResult search(SearchParam param) {

        SearchRequest searchRequest = buildSearchRequest(param);

        SearchResponse response = null;
        try {
            response = restClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            log.error("搜索商品信息失败:", e);
        }
        SearchResult result = buildSearchResult(response, param);

        return result;
    }

    private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {
        SearchResult result = new SearchResult();
        result.setPageNum(param.getPageNum());
        SearchHits hits = response.getHits();

        long total = hits.getTotalHits().value;
        result.setTotal(total);
        result.setTotalPages((int) (total + EsConstant.PRODUCT_PAGESIZE - 1) / EsConstant.PRODUCT_PAGESIZE);

        List<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= result.getTotalPages(); i++) {
            pageNavs.add(i);
        }
        result.setPageNavs(pageNavs);

        List<SkuEsModel> products = new ArrayList<>();
        for (SearchHit hit : hits.getHits()) {
            String source = hit.getSourceAsString();
            SkuEsModel model = JSON.parseObject(source, SkuEsModel.class);
            if (!CollectionUtils.isEmpty(hit.getHighlightFields())) {
                String skuTitle = hit.getHighlightFields().get("skuTitle").getFragments()[0].toString();
                model.setSkuTitle(skuTitle);
            }

            products.add(model);
        }
        result.setProducts(products);


        ParsedLongTerms catalogAgg = response.getAggregations().get("catalog_agg");
        List<SearchResult.CatalogVo> catalogs = catalogAgg.getBuckets().stream().map(item -> {
            SearchResult.CatalogVo catalog = new SearchResult.CatalogVo();
            long catalogId = item.getKeyAsNumber().longValue();
            catalog.setCatalogId(catalogId);
            ParsedStringTerms catalogNameAgg = item.getAggregations().get("catalog_name_agg");
            String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
            catalog.setCatalogName(catalogName);
            return catalog;
        }).collect(Collectors.toList());
        result.setCatalogs(catalogs);


        ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg");
        List<SearchResult.BrandVo> brands = brandAgg.getBuckets().stream().map(item -> {
            SearchResult.BrandVo brand = new SearchResult.BrandVo();
            long id = item.getKeyAsNumber().longValue();
            brand.setBrandId(id);
            ParsedStringTerms brandNameAgg = item.getAggregations().get("brand_name_agg");
            String name = brandNameAgg.getBuckets().get(0).getKeyAsString();
            brand.setBrandName(name);
            ParsedStringTerms brandImgAgg = item.getAggregations().get("brand_img_agg");
            String img = brandImgAgg.getBuckets().get(0).getKeyAsString();
            brand.setBrandImg(img);
            return brand;
        }).collect(Collectors.toList());
        result.setBrands(brands);


        ParsedNested attrAgg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");
        List<SearchResult.AttrVo> attrs = attrIdAgg.getBuckets().stream().map(item -> {
            SearchResult.AttrVo attr = new SearchResult.AttrVo();
            long id = item.getKeyAsNumber().longValue();
            attr.setAttrId(id);
            ParsedStringTerms nameAgg = item.getAggregations().get("attr_name_agg");
            String name = nameAgg.getBuckets().get(0).getKeyAsString();
            attr.setAttrName(name);
            ParsedStringTerms attrValueAgg = nameAgg.getBuckets().get(0).getAggregations().get("attr_value_agg");
            List<String> values = attrValueAgg.getBuckets().stream().map(valueItem -> {
                String value = valueItem.getKeyAsString();
                return value;
            }).collect(Collectors.toList());
            attr.setAttrValue(values);
            return attr;
        }).collect(Collectors.toList());

        result.setAttrs(attrs);

        if (!CollectionUtils.isEmpty(param.getAttrs())) {
            List<SearchResult.NavVo> navs = param.getAttrs().stream().map(item -> {
                SearchResult.NavVo nav = new SearchResult.NavVo();
                String[] s = item.split("_");
                nav.setNavValue(s[1]);
                R r = productFeignService.attrInfo(Long.parseLong(s[0]));
                result.getAttrIds().add(Long.parseLong(s[0]));
                if (r.getCode() == 0) {
                    AttrResponseVo data = r.getData("attr", new TypeReference<AttrResponseVo>() {
                    });
                    nav.setNavName(data.getAttrName());
                } else {
                    nav.setNavName(s[0]);
                }
                String link = replaceQueryString(param, item, "attrs");
                nav.setLink("http://search.gulimall.com/list.html?" + link);
                return nav;
            }).collect(Collectors.toList());
            result.setNavs(navs);
        }

        if (!CollectionUtils.isEmpty(param.getBrandId())) {
            List<SearchResult.NavVo> navs = result.getNavs();
            SearchResult.NavVo nav = new SearchResult.NavVo();

            R r = productFeignService.brandInfos(param.getBrandId());
            if (r.getCode() == 0) {
                StringBuilder builder = new StringBuilder();
                List<BrandVo> brandVos = r.getData("brands", new TypeReference<List<BrandVo>>() {
                });
                String replace = "";
                for (BrandVo brandVo : brandVos) {
                    builder.append(brandVo.getBrandName());
                    replace = replaceQueryString(param, brandVo.getBrandId() + "", "brandId");
                }
                nav.setNavName("品牌");
                nav.setNavValue(builder.toString());
                nav.setLink("http://search.gulimall.com/list.html?" + replace);
            }
            navs.add(nav);
        }


        return result;
    }

    private String replaceQueryString(SearchParam param, String item, String name) {
        String value = null;
        try {
            value = URLEncoder.encode(item, "UTF-8");
            value = value.replace("%28", "\\(").replace("%29", "\\)").replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            log.error("编码失败:", e);
        }
        return param.get_queryString().replaceAll("[&]?" + name + "=" + value, "");
    }

    private SearchRequest buildSearchRequest(SearchParam param) {
        SearchSourceBuilder source = SearchSourceBuilder.searchSource();
        parseQuery(param, source);

        if (!StringUtils.isBlank(param.getSort())) {
            String sort = param.getSort();
            String[] s = sort.split("_");
            SortOrder order = s[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;
            source.sort(s[0].equalsIgnoreCase("hotScore") ? "_score" : s[0], order);
        }

        source.from((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);
        source.size(EsConstant.PRODUCT_PAGESIZE);

        HighlightBuilder highlight = new HighlightBuilder();
        highlight.field("skuTitle");
        highlight.preTags("<b style='color:red'>");
        highlight.postTags("</b>");
        source.highlighter(highlight);

        parseAggs(source);

        System.out.println(source);

        SearchRequest request = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, source);
        return request;
    }

    private void parseQuery(SearchParam param, SearchSourceBuilder source) {
        BoolQueryBuilder bool = QueryBuilders.boolQuery();
        if (!StringUtils.isBlank(param.getKeyword())) {
            bool.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
        }

        if (param.getCatalog3Id() != null) {
            bool.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
        }

        if (!CollectionUtils.isEmpty(param.getBrandId())) {
            bool.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
        }

        if (!CollectionUtils.isEmpty(param.getAttrs())) {
            param.getAttrs().stream().forEach(item -> {
                String[] s = item.split("_");
                BoolQueryBuilder nestedBool = QueryBuilders.boolQuery();
                nestedBool.must(QueryBuilders.termQuery("attrs.attrId", s[0]));
                nestedBool.must(QueryBuilders.termsQuery("attrs.attrValue", s[1].split(":")));

                bool.filter(QueryBuilders.nestedQuery("attrs", nestedBool, ScoreMode.None));

            });
        }

        if (param.getHasStock() != null) {
            bool.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1));
        }

        if (!StringUtils.isBlank(param.getSkuPrice())) {
            String price = param.getSkuPrice();
            String[] s = price.split("_");
            RangeQueryBuilder skuPriceRange = QueryBuilders.rangeQuery("skuPrice");
            if (s.length == 2) {
                skuPriceRange.gt(s[0]);
                skuPriceRange.lt(s[1]);
                bool.filter(skuPriceRange);
            } else if (s.length == 1) {
                if (price.endsWith("_")) {
                    skuPriceRange.gt(s[0]);
                } else if (s[0].endsWith("_")) {
                    skuPriceRange.lt(s[0]);
                }
            }
            bool.filter(skuPriceRange);
        }
        source.query(bool);
    }

    private void parseAggs(SearchSourceBuilder source) {
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg").field("brandId").size(20);
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        brandAgg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
        source.aggregation(brandAgg);

        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(50);
        catalogAgg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        source.aggregation(catalogAgg);

        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(50);
        TermsAggregationBuilder attrNameAgg = AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1);
        attrNameAgg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        attrIdAgg.subAggregation(attrNameAgg);
        attrAgg.subAggregation(attrIdAgg);
        source.aggregation(attrAgg);
    }
}
